package top.mcwebsite.lib.skeleton

import androidx.compose.animation.core.*
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.geometry.toRect
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.node.Ref
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay

fun Modifier.skeleton(
    visible: Boolean,
    color: Color,
    shape: Shape,
    highlight: SkeletonHighlight? = null,
    skeletonFadeTransitionSpec: @Composable Transition.Segment<Boolean>.() -> FiniteAnimationSpec<Float> = { spring() },
    contentFadeTransitionSpec: @Composable Transition.Segment<Boolean>.() -> FiniteAnimationSpec<Float> = { spring() }
): Modifier = composed {
    composed {
        // Value used for caching purposes
        val lastSize = remember { Ref<Size>() }
        val lastLayoutDirection = remember { Ref<LayoutDirection>() }
        val lastOutline = remember { Ref<Outline>() }
        var highlightProgress: Float by remember { mutableStateOf(0f) }
        // This is our cross fade transition
        val transitionState = remember { MutableTransitionState(visible) }.apply {
            targetState = visible
        }
        val transition = updateTransition(transitionState, "placeholder_crossfade")
        val skeletonAlpha by transition.animateFloat(
            transitionSpec = skeletonFadeTransitionSpec,
            label = "skeleton_fade",
            targetValueByState = { skeletonVisible ->
                if (skeletonVisible) 1f else 0f
            }
        )
        val contentAlpha by transition.animateFloat(
            transitionSpec = contentFadeTransitionSpec,
            label = "content_fade",
            targetValueByState = { skeletonVisible -> if (skeletonVisible) 0f else 1f }
        )

        // Run the optional animation spec and update the progress if the skeleton is visible
        val animationSpec = highlight?.animationSpec
        if (animationSpec != null && (visible || skeletonAlpha >= 0.01f)) {
            val infiniteTransition = rememberInfiniteTransition(label = "")
            highlightProgress =
                infiniteTransition.animateFloat(
                    initialValue = 0f,
                    targetValue = 1f,
                    animationSpec = animationSpec, label = ""
                ).value
        }
        val paint = remember { Paint() }
        remember(shape, color, highlight) {
            drawWithContent {
                // Draw the composable content first
                if (contentAlpha in 0.01f..0.99f) {
                    // If the content alpha is between 1% and 99%, draw it in a layer with the alpha applied
                    paint.alpha = contentAlpha
                    withLayer(paint) {
                        with(this@drawWithContent) {
                            drawContent()
                        }
                    }
                } else if (contentAlpha > 0.99f) {
                    drawContent()
                }
                if (skeletonAlpha in 0.01f..0.99f) {
                    // If the skeleton alpha is between 1% and 99%, draw it in a layer with the alpha applied
                    paint.alpha = skeletonAlpha
                    withLayer(paint) {
                        lastOutline.value = drawSkeleton(
                            shape = shape,
                            color = color,
                            highlight = highlight,
                            progress = highlightProgress,
                            lastOutline = lastOutline.value,
                            lastLayoutDirection = lastLayoutDirection.value,
                            lastSize = lastSize.value,
                        )
                    }
                } else if (skeletonAlpha >= 0.99) {
                    // If the skeleton alpha is > 99%, draw it with no alpha
                    lastOutline.value = drawSkeleton(
                        shape = shape,
                        color = color,
                        highlight = highlight,
                        progress = highlightProgress,
                        lastOutline = lastOutline.value,
                        lastLayoutDirection = lastLayoutDirection.value,
                        lastSize = lastSize.value
                    )
                }
                // Keep track of the last size & layout direction
                lastSize.value = size
                lastLayoutDirection.value = layoutDirection
            }
        }
    }
}

private fun DrawScope.drawSkeleton(
    shape: Shape,
    color: Color,
    highlight: SkeletonHighlight?,
    progress: Float,
    lastOutline: Outline?,
    lastLayoutDirection: LayoutDirection?,
    lastSize: Size?,
): Outline? {
    // shortcut to avoid Outline calculation and allocation
    if (shape == RectangleShape) {
        // Draw the initial background color
        drawRect(color = color)
        if (highlight != null) {
            drawRect(
                brush = highlight.brush(progress, size),
                alpha = highlight.alpha(progress)
            )
        }
        // We didn't create an outline so return null
        return null
    }

    // Otherwise we need to create an outline from the shape
    val outline = lastOutline.takeIf {
        size == lastSize && layoutDirection == lastLayoutDirection
    } ?: shape.createOutline(size, layoutDirection, this)

    // Draw the skeleton color
    drawOutline(outline = outline, color = color)
    if (highlight != null) {
        drawOutline(
            outline = outline,
            brush = highlight.brush(progress, size),
            alpha = highlight.alpha(progress)
        )
    }
    return outline
}

fun DrawScope.withLayer(
    paint: Paint,
    drawBlock: DrawScope.() -> Unit
) = drawIntoCanvas { canvas ->
    canvas.saveLayer(size.toRect(), paint)
    drawBlock()
    canvas.restore()
}

@Preview
@Composable
fun Test() {
    val visible = remember {
        mutableStateOf(true)
    }
    LaunchedEffect(key1 = Unit) {
        delay(5000)
        visible.value = false
    }
    Column {
        Text(
            text = "Hello World!", modifier = Modifier.skeleton(
                visible.value,
                color = Color.Gray,
                shape = RectangleShape,
                highlight = SkeletonHighlight.shimmer(
                    highlightColor = Color.White
                )
            )
        )
        Spacer(modifier = Modifier.height(10.dp))
        Text(
            text = "Hello World!", modifier = Modifier.skeleton(
                visible.value,
                color = Color.Gray,
                shape = RectangleShape,
                highlight = SkeletonHighlight.shimmer(
                    highlightColor = Color.White
                )
            )
        )
    }
}